home *** CD-ROM | disk | FTP | other *** search
/ Stone Design / Stone Design.iso / Stone_Friends / Wave / WavesWorld / Source / IBPalettes / WWTCLKit / PAThumbWheelCell.m < prev    next >
Encoding:
Text File  |  1995-03-22  |  25.0 KB  |  677 lines

  1. #import "PAThumbWheelCell.h"
  2. //#import "PAThumbWheelCellDrawing.h" // stuck this functionality in here...
  3. #import "PAThumbWheel.h"
  4.  
  5. /******************************************************************************
  6.     PAThumbWheelCell
  7.     
  8. PAThumbWheel offers the functionality of Slider plus the features that you would expect from a real thumbwheel (including 2 3/4 D Graphics!).
  9.  
  10. PAThumbWheel has a linear display mode and a radial display mode and offers the ability to assign a value to the visible region of the control as well as an absolute value that the ThumbWheel will either ignore, bound to or wrap around.
  11.  
  12. PAThumbWheel can also return relative values via its -relativeIntValue & -relativeFloatValue methods. A snap back option allows mouse loops to start from and return to a base value.
  13.  
  14. Copyright 1992, Jeff Martin. (jmartin@next.com 415-780-3833)
  15. ******************************************************************************/
  16. #define PI            3.141592654
  17. #define HALF_PI        1.570796327
  18. #define DEG_TO_RAD 0.0174532925199433        //  PI/180.0
  19. #define SIN(x) (sin(DEG_TO_RAD*(x)))
  20. #define COS(x) (cos(DEG_TO_RAD*(x)))
  21. #define TAN(x) (tan(DEG_TO_RAD*(x)))
  22.  
  23. #define RAD_TO_DEG 57.295779513082320876    //  180.0/PI
  24. #define ASIN(x) (asin(x)*RAD_TO_DEG)
  25. #define ACOS(x) (acos(x)*RAD_TO_DEG)
  26. #define ATAN(x) (atan(x)*RAD_TO_DEG)
  27. // A mod function for floating values
  28. #define MOD(x,y) ((x) - (y)*(int)((float)(x)/(y)))
  29. // Keep 'a' between x and y
  30. #define CLAMP(a,x,y) (MAX((x), MIN((y), (a))))
  31. // Keep 'a' between 'x' and 'y' by wrapping it to the other side
  32. #define CLAMP_WITH_WRAP(a,x,y)                                              \
  33. ( ((a) < (x)) ? ((y) - MOD(((x)-(a)),((y)-(x)))) :                         \
  34. ( ((a) > (y)) ? ((x) + MOD(((a)-(y)),((y)-(x)))) : (a) ) )
  35. // Is a between x and y
  36. #define ISBETWEEN(a,x,y) (((a)>=(x))&&((a)<=(y)))
  37. #define EQUAL(a,b) (ABS((a)-(b))<0.00001)
  38.  
  39. @implementation PAThumbWheelCell
  40.  
  41. // Set reasonable defaults
  42. - init
  43. {
  44.     [super init];
  45.     [self setType:NX_TEXTCELL];
  46.     [self setHorizontal];
  47.  
  48.     [self setLinear]; [self setUnbounded];
  49.     [self setVisibleMax:1.0]; [self setVisibleMin:-1.0];
  50.     [self setAbsoluteMax:2.0]; [self setAbsoluteMin:-2.0];
  51.     [self setSnapsBack:YES]; [self setSnapBackValue:0.0];
  52.     [self setDashInterval:10]; [self setShowMainDash:YES];
  53.     [self setColor:NX_COLORLTGRAY];
  54.     
  55.     [self sendActionOn:NX_MOUSEDOWNMASK|NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
  56.         colorChanged = NO;
  57.  
  58.     return self;
  59. }
  60.  
  61. - awake
  62. {  colorChanged = NO;
  63.    return [super awake];
  64. }
  65.  
  66. - copyFromZone:(NXZone *)zone
  67. {
  68.   id newCopy = [super copyFromZone:zone];
  69.  
  70.   return newCopy;
  71. }
  72.  
  73.  
  74. // Override trackMouse to provide an infinitely large area(continuous tracking)
  75. - (BOOL)trackMouse:(NXEvent *)event inRect:(const NXRect *)rect ofView:view
  76. { cellFrame = *rect; return [super trackMouse:event inRect:NULL ofView:view]; }
  77.  
  78. // Override to return YES (always track mouse)
  79. - (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:aView { return YES;}
  80.  
  81. // Override from cell to track mouse
  82. - (BOOL)continueTracking:(const NXPoint *)lastPoint 
  83.     at:(const NXPoint *)currPoint inView:view
  84. {
  85.     float currPointVal =[self floatValueAtPoint:*currPoint forFrame:cellFrame];
  86.     float lastPointVal =[self floatValueAtPoint:*lastPoint forFrame:cellFrame];
  87.     float mouseScale = ([NXApp currentEvent]->flags&NX_ALTERNATEMASK)? .1 : 1;
  88.     
  89.     // If TW is bounded and at absolute bound, only track when mouse comes back
  90.     if([self isBounded]) {
  91.         // Return if we are already at absoluteMax and currPoint is greater
  92.         if(EQUAL([self floatValue], [self absoluteMax])) {
  93.             if(currPointVal > [self absoluteMax]) return YES;
  94.             else lastPointVal = [self absoluteMax];
  95.         }
  96.         // Return if we are already at absoluteMin and currPoint is less
  97.         if(EQUAL([self floatValue], [self absoluteMin])) {
  98.             if(currPointVal < [self absoluteMin]) return YES;
  99.             else lastPointVal = [self absoluteMin];
  100.         }
  101.     }
  102.     
  103.     // Set the float value relative to last point
  104.     [self setFloatValue:[self floatValue] + 
  105.         (currPointVal - lastPointVal)*mouseScale];
  106.     
  107.     // Redraw control
  108.     [view display];
  109.     return YES;    // Always continue to track mouse
  110. }
  111.  
  112.  
  113. /******************************************************************************
  114.     floatValueAtPoint:forFrame:
  115.     
  116.     This method gives the value that corresponds to a point with respect to the given frame and the visible range. When in radial mode, the point on the thumbwheel is approximated with a power series for arcCos to get legal values for points outside of the frame.
  117. ******************************************************************************/
  118. - (float)floatValueAtPoint:(NXPoint)point forFrame:(NXRect)frame
  119. {
  120.     float pos = [self isVertical] ? point.y : point.x;
  121.     float base = [self isVertical] ? NX_Y(&frame) : NX_X(&frame);
  122.     float width = [self isVertical] ? NX_HEIGHT(&frame) : NX_WIDTH(&frame);
  123.     float value;
  124.     
  125.     if([self isLinear])
  126.         value = [self visibleMin] + (pos - base)/width*[self visibleRange];
  127.     else {
  128.         float radius = width/2;
  129.         float midP = base + radius;
  130.         float x = (midP - pos)/radius;
  131.         // Get degrees by pwr series approximation of ArcCos (Pi/2 - x - x^3/6)
  132.         float alpha = (HALF_PI - x - x*x*x/6);
  133.         // Convert degrees to TW coords
  134.         value = [self visibleMin] + alpha/PI*[self visibleRange];
  135.     }
  136.     return value;
  137. }
  138.     
  139. // Override from cell to snapback if neccessary
  140. - stopTracking:(const NXPoint *)lastPoint at:(const NXPoint *)stopPoint
  141.     inView:view mouseIsUp:(BOOL)flag
  142. {
  143.     // If the ThumbWheel is in snap back mode, snap it back and redraw
  144.     if([self snapsBack]) {
  145.         [self setFloatValue:[self snapBackValue]];
  146.         [self resetRelativeValue];
  147.         [view display];
  148.     }
  149.     return [super stopTracking:lastPoint at:stopPoint inView:view 
  150.         mouseIsUp:flag];
  151. }
  152.  
  153. // Direction is either DIRECTION_HORIZONTAL or DIRECTION_VERTICAL
  154. - (int)direction { return direction; }
  155. - setDirection:(int)dir
  156. { direction = dir; [image free]; image = NULL; return self; }
  157. - (BOOL)isVertical { return direction == DIRECTION_VERTICAL; }
  158. - setVertical { direction = DIRECTION_VERTICAL; return self; }
  159. - (BOOL)isHorizontal { return direction == DIRECTION_HORIZONTAL; }
  160. - setHorizontal { direction = DIRECTION_HORIZONTAL; return self; }
  161.  
  162. /******************************************************************************
  163.     displayMode, setDisplayMode
  164.     
  165.     The displayMode is either DISPLAY_MODE_LINEAR or DISPLAY_MODE_RADIAL. Linear displays a flat ruler type control whereas radial displays a 3D thumbwheel that actually looks curved .
  166. ******************************************************************************/
  167. - (int)displayMode { return displayMode; }
  168. - setDisplayMode:(int)mode { displayMode = mode; return self; }
  169. - (BOOL)isRadial { return displayMode == DISPLAY_MODE_RADIAL; }
  170. - setRadial { return [self setDisplayMode:DISPLAY_MODE_RADIAL]; }
  171. - (BOOL)isLinear { return displayMode == DISPLAY_MODE_LINEAR; }
  172. - setLinear { return [self setDisplayMode:DISPLAY_MODE_LINEAR]; }
  173.  
  174. /******************************************************************************
  175.     intValue, setIntValue, floatValue, setFloatValue
  176.     
  177.     These methods are overridden to allow us to calculate relative values and to constrain the value with respect to the absolute mode and absolute values.
  178. ******************************************************************************/
  179. - (int)intValue { return (int)[self floatValue]; }
  180. - setIntValue:(int)val { [self setFloatValue:val]; return self; }
  181. - (float)floatValue { return floatValue; }
  182. - setFloatValue:(float)val
  183.     // Clamp or Wrap newValue wrt the absoluteMode
  184.     if(!ISBETWEEN(val, [self absoluteMin], [self absoluteMax])) {
  185.         if([self isBounded]) val = 
  186.             CLAMP(val, [self absoluteMin], [self absoluteMax]);
  187.         else if([self isWrapped]) val = 
  188.             CLAMP_WITH_WRAP(val, [self absoluteMin], [self absoluteMax]);
  189.     }
  190.  
  191.     // Store last float value and set float value
  192.     lastFloatValue = floatValue; floatValue = val;
  193.     return self;
  194. }
  195. - (float)lastFloatValue { return lastFloatValue; }
  196. - setLastFloatValue:(float)value { lastFloatValue=value; return self;}
  197.  
  198. /******************************************************************************
  199.     visibleMax, visibleMin, visibleRange, middleValue
  200.     
  201.     VisibleMax and visibleMin are the values of the thumbwheel at either end; max value is at right/top, min is at left/bottom. middleValue is the value of the TW at the center (wrt visibleMin and visibleMax).
  202. ******************************************************************************/
  203. - (float)visibleMax { return visMax; }
  204. - setVisibleMax:(float)max { visMax = max; return self; }
  205. - (float)visibleMin { return visMin; }
  206. - setVisibleMin:(float)min { visMin = min; return self; }
  207. - (float)visibleRange { return [self visibleMax] - [self visibleMin]; }
  208. - (float)middleValue { return [self visibleMin] + [self visibleRange]/2; }
  209.  
  210. /******************************************************************************
  211.     absoluteMode, absoluteMax, absoluteMin
  212.     
  213.     The absolute mode refers to ThumbWheel values that exceed the visible range. ABSOLUTE_UNBOUNDED means that the TW can be dragged as high or low as desired. ABSOLUTE_BOUNDED means that the TW will be clamped to some arbitrarily large value. ABSOLUTE_WRAPPED means that the TW will wrap from the absoluteMax to the absoluteMin (and vise-versa) when applicable. AbsoluteMax is the value off to the right and up. AbsoluteMin us the value off to the left and down.
  214. ******************************************************************************/
  215. - (int)absoluteMode        { return absMode; }
  216. - setAbsoluteMode:(int)mode { absMode = mode; return self; }
  217. - (BOOL)isUnbounded        { return absMode == ABSOLUTE_UNBOUNDED;  }
  218. - setUnbounded            { return [self setAbsoluteMode:ABSOLUTE_UNBOUNDED]; }
  219. - (BOOL)isBounded         { return absMode == ABSOLUTE_BOUNDED; }
  220. - setBounded            { return [self setAbsoluteMode:ABSOLUTE_BOUNDED]; }
  221. - (BOOL)isWrapped        { return absMode == ABSOLUTE_WRAPPED; }
  222. - setWrapped            { return [self setAbsoluteMode:ABSOLUTE_WRAPPED]; }
  223. - (float)absoluteMax    { return absMax; }
  224. - setAbsoluteMax:(float)value { absMax = value; return self; }
  225. - (float)absoluteMin     { return absMin; }
  226. - setAbsoluteMin:(float)value { absMin = value; return self; }
  227. - (float)absoluteRange    { return [self absoluteMax] - [self absoluteMin]; }
  228.  
  229. /******************************************************************************
  230.     relativeIntValue, relativeFloatValue, resetRelativeValue
  231.     
  232.     These two methods return the change of the value since the last iteration. This is useful for a relative method call ( rotateBy: as opposed to rotateTo:). resetRelativeValue sets the relative change to zero (typically only called internally when snapping back).
  233. ******************************************************************************/
  234. - (int)relativeIntValue
  235. { return [self intValue] - (int)[self lastFloatValue];}
  236. - (float)relativeFloatValue
  237. { return [self floatValue] - [self lastFloatValue]; }
  238. - resetRelativeValue { [self setLastFloatValue:[self floatValue]];return self;}
  239.  
  240. /******************************************************************************
  241.     snapsBack, snapBackValue
  242.     
  243.     It is sometimes useful to have the thumbwheel snap back to some value (zero by default) so that it can be used for relative modification of values (rotate by as opposed to rotateTo:).
  244. ******************************************************************************/
  245. - (BOOL)snapsBack                { return snapsBack; }
  246. - setSnapsBack:(BOOL)flag        { snapsBack = flag; return self; }
  247. - (float)snapBackValue            {return snapBackValue; }
  248. - setSnapBackValue:(float)value    { snapBackValue = value;return self;}
  249.  
  250. /******************************************************************************
  251.     dashInterval, setDashInterval, setDashIntervalEqual
  252.  
  253.      The dash interval is either in degrees (DISPLAY_MODE_RADIAL) or PostScript 
  254. points (DISPLAY_MODE_LINEAR). The default is 10 of each.
  255. ******************************************************************************/
  256. - (float)dashInterval            { return dashInterval; }
  257. - setDashInterval:(float)val
  258.   // wave fixed this.  This was a crasher...
  259.   if (val < 1.0) 
  260.   {  dashInterval = 1.0;
  261.   } 
  262.   else
  263.   {  dashInterval = val;
  264.   }
  265.   return self; 
  266. }
  267.  
  268. /******************************************************************************
  269.     showMainDash, setShowMainDash
  270.  
  271.      The main dash is the dash in the center of the control and gives feedback as to the absolute value of the control. This should be set to NO for TW that only provide relative values.
  272. ******************************************************************************/
  273. - (BOOL)showMainDash             {return showMainDash;}
  274. - setShowMainDash:(BOOL)flag    { showMainDash = flag; return self; }
  275.  
  276. /******************************************************************************
  277.     color, setColor
  278.  
  279.      These methods manipulate the predominant color of the ThumbWheel. Radial ThumbWheel are of course shades of these. The default is NX_COLORLTGRAY. 
  280. ******************************************************************************/
  281. - (NXColor)color            {return color;}
  282. - setColor:(NXColor)c        { color = c; return self; }
  283.  
  284. /******************************************************************************
  285.     shift
  286.     
  287.      The shift is how much the dashes are shifted by to achieve the animation of motion it is in points. It is calculated from the visibleRange and the physicalRange (frame).
  288. ******************************************************************************/
  289. - (int)shift:(const NXRect *)frame
  290. {    
  291.     if([self isLinear]) {
  292.         if([self isHorizontal])
  293.             return (int)(([self floatValue] - [self visibleMin]) / 
  294.                 [self visibleRange]*NX_WIDTH(frame) + .5);
  295.         else
  296.             return (int)(([self floatValue] - [self visibleMin]) / 
  297.                 [self visibleRange]*NX_HEIGHT(frame) +.5);
  298.     }
  299.     else return (int)(([self floatValue] - [self visibleMin])/ 
  300.         [self visibleRange]*180 + .5);
  301. }
  302.  
  303. // Override highlight:inView:lit: so that it does nothing
  304. - highlight:(const NXRect *)frame inView:view lit:(BOOL)flag { return self; }
  305.  
  306. // Override this so that we track mouse whether or not it is on top of us.
  307. + (BOOL)prefersTrackingUntilMouseUp { return YES; }
  308.  
  309.  
  310.  
  311. /******************************************************************************
  312.  
  313.      The Read and Write Methods are for archival.
  314. ******************************************************************************/
  315. - write:(NXTypedStream *)stream
  316. {
  317.     [super write:stream];
  318.  
  319.     NXWriteType(stream, "i", &displayMode);
  320.     NXWriteType(stream, "i", &direction);
  321.  
  322.     NXWriteType(stream, "f", &floatValue);
  323.     NXWriteType(stream, "f", &lastFloatValue);
  324.  
  325.     NXWriteType(stream, "f", &visMax);
  326.     NXWriteType(stream, "f", &visMin);
  327.  
  328.     NXWriteType(stream, "i", &absMode);
  329.     NXWriteType(stream, "f", &absMax);
  330.     NXWriteType(stream, "f", &absMin);
  331.  
  332.     NXWriteType(stream, "c", &snapsBack);
  333.     NXWriteType(stream, "f", &snapBackValue);
  334.  
  335.     NXWriteType(stream, "i", &dashInterval);
  336.     NXWriteType(stream, "c", &showMainDash);
  337.  
  338.     NXWriteColor(stream, color);
  339.     return self;
  340. }
  341.  
  342. - read:(NXTypedStream *)stream
  343. {
  344.     [super read:stream];
  345.  
  346.     NXReadType(stream, "i", &displayMode);
  347.     NXReadType(stream, "i", &direction);
  348.  
  349.  
  350.     NXReadType(stream, "f", &floatValue);
  351.     NXReadType(stream, "f", &lastFloatValue);
  352.  
  353.     NXReadType(stream, "f", &visMax);
  354.     NXReadType(stream, "f", &visMin);
  355.  
  356.     NXReadType(stream, "i", &absMode);
  357.     NXReadType(stream, "f", &absMax);
  358.     NXReadType(stream, "f", &absMin);
  359.  
  360.     NXReadType(stream, "c", &snapsBack);
  361.     NXReadType(stream, "f", &snapBackValue);
  362.  
  363.     NXReadType(stream, "i", &dashInterval);
  364.     NXReadType(stream, "c", &showMainDash);
  365.  
  366.     color = NXReadColor(stream);
  367.         colorChanged = YES;
  368.     return self;
  369. }
  370.  
  371. - free
  372. {
  373.     [image free];
  374.     return [super free];
  375. }
  376.  
  377. // stuff stolen from PAThumbWheelCellDrawing
  378.  
  379. #define NOTEQUAL(a,b) (ABS((a)-(b))>0.0001)
  380. #define EVEN(x) (!(((int)(x))%2))
  381.  
  382. - drawSelf:(const NXRect *)theFrame inView:view
  383. {
  384.     NXRect frame = *theFrame;
  385.     float *pnts;                        // Used for user path of dashes
  386.     char *ops;                            // Used for user path of dashes
  387.     int pntCount, opCount;                // Used for user path of dashes
  388.     float bbox[4] = { NX_X(theFrame), NX_Y(theFrame),
  389.         NX_MAXX(theFrame), NX_MAXY(theFrame) };
  390.     
  391.     // Inset by two to allow for bezeled border
  392.     NXInsetRect(&frame,2,2);
  393.  
  394.     // Draw the background
  395.     if([self isLinear]) {
  396.         NXDrawGrayBezel(theFrame, theFrame);
  397.         NXSetColor(color);
  398.         NXRectFill(&frame);
  399.     }
  400.     else { // if([self isRadial])
  401.         NXSize imageSize;
  402.         [image getSize:&imageSize];
  403.         if(NOTEQUAL(imageSize.width,NX_WIDTH(theFrame)) ||
  404.             NOTEQUAL(imageSize.height,NX_HEIGHT(theFrame)) || colorChanged)
  405.             [self generateImage:theFrame];
  406.         [image composite:NX_COPY toPoint:&theFrame->origin];
  407.     }
  408.     
  409.     // Get the userpath for the dashes
  410.     [self getDashesForFrame:&frame :&pnts :&pntCount :&ops :&opCount];
  411.  
  412.     // Draw dashes once for white part of groove
  413.     if([self isHorizontal]) PStranslate(1,0); else PStranslate(0,-1);
  414.  
  415.     // Draw linear white dashes
  416.     if([self isLinear]) {
  417.         NXSetColor(PAScaleRGBColor(color, 1.5));
  418.         DPSDoUserPath(pnts, pntCount, dps_float, ops, opCount, bbox, 
  419.             dps_ustroke);
  420.     }
  421.     
  422.     // Break up radial white dashes to fade a little bit at ends
  423.     else {
  424.         int i = 0, j = 0, d = [self isHorizontal]? 0 : 1;
  425.         float oneQuarterOfX = [self isHorizontal]? (NX_X(&frame) + 
  426.             NX_WIDTH(&frame)/4) : (NX_Y(&frame) + NX_HEIGHT(&frame)/4);
  427.         float threeQuartersOfX =[self isHorizontal]? (NX_X(&frame) + 
  428.             3*NX_WIDTH(&frame)/4) : (NX_Y(&frame) + 3*NX_HEIGHT(&frame)/4);
  429.  
  430.         NXSetColor(color);
  431.         while((i < pntCount) && (pnts[i+d] < oneQuarterOfX)) i += 4; j = i;
  432.         if(i>0) DPSDoUserPath(pnts, i, dps_float, ops, i/2, bbox, dps_ustroke);
  433.     
  434.         NXSetColor(PAScaleRGBColor(color, 1.5));
  435.         while((j < pntCount) && (pnts[j+d] < threeQuartersOfX)) j+=4;
  436.         if(j>i) DPSDoUserPath(&pnts[i], j-i, dps_float, &ops[i/2], j/2-i/2,
  437.             bbox, dps_ustroke);
  438.     
  439.         NXSetColor(color);
  440.         if(pntCount>j) DPSDoUserPath(&pnts[j], pntCount-j, dps_float, 
  441.             &ops[j/2], opCount-j/2, bbox, dps_ustroke);
  442.     }
  443.     
  444.     if([self isHorizontal]) PStranslate(-1,0); else PStranslate(0,1);
  445.  
  446.     // Draw again for dark part of groove
  447.     if([self isLinear]) NXSetColor(PAScaleRGBColor(color, .5)); 
  448.     else NXSetColor(NX_COLORBLACK);
  449.     DPSDoUserPath(pnts, pntCount, dps_float, ops, opCount, bbox, dps_ustroke);
  450.     
  451.     // If disabled then dim ThumbWheel out
  452.     if(![self isEnabled]) {
  453.         NXSetColor(NX_COLORWHITE); PSsetalpha(.5);
  454.         PScompositerect(NX_X(&frame), NX_Y(&frame), NX_WIDTH(&frame),
  455.             NX_HEIGHT(&frame), NX_SOVER);
  456.     }
  457.     
  458.     // Free user path variables
  459.     free(pnts); free(ops);
  460.     return self;
  461. }
  462.  
  463.  
  464. // wave added these two routines...
  465. - setColorChanged:(BOOL)flag { colorChanged = flag;  return self; }
  466. - (BOOL)colorChanged { return colorChanged; }
  467.  
  468.  
  469. - getDashesForFrame:(const NXRect *)frame :(float **)PNTS :(int *)PNTCOUNT :(char **)OPS :(int *)OPCOUNT
  470. {
  471.     // Get dashInterval and shift
  472.     int dashInt = [self dashInterval];
  473.     int shift = [self shift:frame];
  474.  
  475.     // Calculate how many dashes there will be and alloc space for pnts and ops
  476.     int dashCount = 2 + ([self isRadial] ? 180/dashInt : 
  477.         ([self isVertical] ? NX_HEIGHT(frame) : NX_WIDTH(frame)) / dashInt);
  478.     float *pnts = malloc(sizeof(float)*dashCount*4); // (moveto+lineto)*(x+y)=4
  479.     char *ops     = malloc(sizeof(char)*dashCount*2);  // (moveto+lineto) = 2
  480.     int i=0, j=0;
  481.     
  482.     // Calculate dash sizes
  483.     int dashBase     = [self isVertical] ? NX_X(frame) : NX_Y(frame);
  484.     int dashHeight    = [self isVertical] ? NX_WIDTH(frame) : NX_HEIGHT(frame);
  485.     int dashMinTop     = dashBase + dashHeight*.25;
  486.     int dashMajTop     = dashBase + dashHeight*.5;
  487.     int dashTop        = dashBase + dashHeight;
  488.     
  489.     float base        =    [self isVertical] ? NX_Y(frame) : NX_X(frame);
  490.     float width        =    [self isVertical]? NX_HEIGHT(frame) : NX_WIDTH(frame);
  491.     float halfWidth    =    width/2;
  492.     float mid        =    base + halfWidth;
  493.     float top        =    base + width;
  494.  
  495.     float mainDash;
  496.     float x;
  497.     
  498.     // Calculate whether first dash is a major one. 
  499.     BOOL isMajor    = (shift>=0)? EVEN(shift/dashInt) : !EVEN(shift/dashInt);
  500.         
  501.     // Calculate Linear dashes
  502.     if([self isLinear]) {
  503.         // Set Main dash
  504.         mainDash = base + shift;
  505.  
  506.         // Calculate starting point and set the dashes
  507.         x = base+CLAMP_WITH_WRAP(shift,0,dashInt)%((shift>=0)? dashInt:999999);
  508.         if([self isVertical]) while(x<top) {
  509.             pnts[i++] = dashBase; pnts[i++] = x;
  510.             pnts[i++] = isMajor ? dashMajTop : dashMinTop;
  511.             if(EQUAL(x, mainDash)&&[self showMainDash]) pnts[i-1] = dashTop;
  512.             pnts[i++] = x;
  513.             x += dashInt; isMajor = !isMajor;
  514.         }
  515.         else while(x<top) {
  516.             pnts[i++] = x; pnts[i++] = dashBase;
  517.             pnts[i++] = x; pnts[i++] = isMajor ? dashMajTop : dashMinTop;
  518.             if(EQUAL(x, mainDash)&&[self showMainDash]) pnts[i-1] = dashTop;
  519.             x += dashInt; isMajor = !isMajor;
  520.         }
  521.     }
  522.  
  523.     // Calculate Radial Dashes
  524.     else {
  525.         // This is used to convert the degrees of a dash to a location(in pnts)
  526.         float linDash = 0;
  527.         
  528.         // Inset dash size for beveled edges
  529.         dashBase++; dashTop--;
  530.         
  531.         // Calc Main dash if we show it and it is in sight
  532.         mainDash = mid - IntCos(shift)*halfWidth;
  533.  
  534.         // Calculate the starting point and set the dashes
  535.         x = CLAMP_WITH_WRAP(shift, 0, dashInt)%((shift>=0)? dashInt:999999);
  536.         if([self isVertical]) while(x<180) {
  537.             linDash = mid - IntCos(x)*halfWidth;
  538.             pnts[i++] = dashBase; pnts[i++] = linDash;
  539.             pnts[i++] = isMajor ? dashMajTop : dashMinTop;
  540.             // Check to see if this is a valid main dash
  541.             if(isMajor && EQUAL(linDash,mainDash) && [self showMainDash] &&
  542.                 ISBETWEEN(shift, 0, 180)) pnts[i-1]=dashTop;
  543.             pnts[i++] = linDash;
  544.             x += dashInt; isMajor = !isMajor;
  545.         }
  546.         else while(x<180) {
  547.             linDash = mid - IntCos(x)*halfWidth;
  548.             pnts[i++] = linDash; pnts[i++] = dashBase;
  549.             pnts[i++] = linDash; pnts[i++] = isMajor ? dashMajTop : dashMinTop;
  550.             // Check to see if this is a valid main dash
  551.             if(isMajor && EQUAL(linDash,mainDash) && [self showMainDash] &&
  552.                 ISBETWEEN(shift, 0, 180)) pnts[i-1]=dashTop;
  553.             x += dashInt; isMajor = !isMajor;
  554.         }
  555.     }
  556.         
  557.     // fill the ops array with dps_moveto and dps_lineto
  558.     while(j<i/2) { ops[j++] = dps_moveto; ops[j++] = dps_lineto; }
  559.      
  560.     // Set the passed in pointers to the arrays and the counts and return
  561.     *PNTS = pnts; *OPS = ops; *PNTCOUNT = i; *OPCOUNT = j;
  562.     return self;
  563. }
  564.  
  565. - generateImage:(const NXRect *)theFrame
  566. {
  567.     float circleHeight;
  568.     float x, rad, rad2, mid;
  569.     NXRect frame = *theFrame;
  570.  
  571.     if(!image) image = [[NXImage alloc] init];
  572.     [image setSize:&frame.size];
  573.     [image lockFocus];
  574.     NXDrawGrayBezel(&frame,&frame);
  575.     NXInsetRect(&frame,2,2);
  576.     PSsetlinewidth(0.0); 
  577.  
  578.     // Draw horizonal version
  579.     if(direction == DIRECTION_HORIZONTAL) {
  580.         rad = NX_WIDTH(&frame)/2.0; rad2 = rad*rad;
  581.         mid = NX_MIDX(&frame);
  582.  
  583.         for(x = NX_X(&frame); x <= NX_MIDX(&frame); x++) {
  584.             float topx = NX_MAXX(&frame) - (x - NX_X(&frame));
  585.             circleHeight = sqrt(rad2 - (mid-x)*(mid-x))/rad;
  586.  
  587.             PSnewpath();
  588.             NXSetColor(PAScaleRGBColor(color, .5*circleHeight));
  589.             PSmoveto(x, NX_Y(&frame));        PSlineto(x, NX_Y(&frame) + 2);
  590.             PSmoveto(topx, NX_Y(&frame));    PSlineto(topx, NX_Y(&frame) + 2);
  591.             PSstroke();
  592.  
  593.             PSnewpath();
  594.             NXSetColor(PAScaleRGBColor(color, circleHeight));
  595.             PSmoveto(x, NX_Y(&frame)+2);    PSlineto(x, NX_MAXY(&frame) - 1);
  596.             PSmoveto(topx, NX_Y(&frame)+2);PSlineto(topx,NX_MAXY(&frame) -1);
  597.             PSstroke();
  598.  
  599.             PSnewpath();
  600.             NXSetColor(PAScaleRGBColor(color,1.5*circleHeight));
  601.             PSmoveto(x, NX_MAXY(&frame)-1);    PSlineto(x, NX_MAXY(&frame));
  602.             PSmoveto(topx, NX_MAXY(&frame)-1);PSlineto(topx, NX_MAXY(&frame));
  603.             PSstroke();
  604.         }
  605.     }
  606.     else {
  607.         rad = NX_HEIGHT(&frame)/2.0; rad2 = rad*rad;
  608.         mid = NX_MIDY(&frame);
  609.         for(x = NX_Y(&frame); x <= NX_MIDY(&frame); x++) {
  610.             float topx = NX_MAXY(&frame) - (x - NX_Y(&frame));
  611.             circleHeight = sqrt(rad2 - (mid-x)*(mid-x))/rad;
  612.  
  613.             PSnewpath();
  614.             NXSetColor(PAScaleRGBColor(color, 1.5*circleHeight));
  615.             PSmoveto(NX_X(&frame), x);        PSlineto(NX_X(&frame) + 2, x);
  616.             PSmoveto(NX_X(&frame), topx);    PSlineto(NX_X(&frame) + 2, topx);
  617.             PSstroke();
  618.  
  619.             PSnewpath();
  620.             NXSetColor(PAScaleRGBColor(color, circleHeight));
  621.             PSmoveto(NX_X(&frame)+2, x);    PSlineto(NX_MAXX(&frame) - 1, x);
  622.             PSmoveto(NX_X(&frame)+2, topx);    PSlineto(NX_MAXX(&frame) - 1,topx);
  623.             PSstroke();
  624.  
  625.             PSnewpath();
  626.             NXSetColor(PAScaleRGBColor(color, .5*circleHeight));
  627.             PSmoveto(NX_MAXX(&frame)-1, x);    PSlineto(NX_MAXX(&frame), x);
  628.             PSmoveto(NX_MAXX(&frame)-1, topx);PSlineto(NX_MAXX(&frame), topx);
  629.             PSstroke();
  630.         }
  631.     }
  632.  
  633.     [image unlockFocus];
  634.         colorChanged = NO;
  635.     return self;
  636. }
  637.  
  638. - (const char *)getInspectorClassName { return "PAThumbWheelInspector"; }
  639.  
  640. NXColor PAScaleRGBColor(NXColor c, float scale)
  641. { return NXConvertRGBToColor(NXRedComponent(c)*scale, NXGreenComponent(c)*
  642. scale, NXBlueComponent(c)*scale); }
  643.  
  644. float _IntSin[91] ={0, 0.017452406, 0.034899497, 0.052335956, 0.069756474, 
  645.   0.087155743, 0.10452846, 0.12186934, 0.1391731, 
  646.   0.15643447, 0.17364818, 0.190809, 0.20791169, 
  647.   0.22495105, 0.2419219, 0.25881905, 0.27563736, 
  648.   0.2923717, 0.30901699, 0.32556815, 0.34202014, 
  649.   0.35836795, 0.37460659, 0.39073113, 0.40673664, 
  650.   0.42261826, 0.43837115, 0.4539905, 0.46947156, 
  651.   0.48480962, 0.5, 0.51503807, 0.52991926, 0.54463904, 
  652.   0.5591929, 0.57357644, 0.58778525, 0.60181502, 
  653.   0.61566148, 0.62932039, 0.64278761, 0.65605903, 
  654.   0.66913061, 0.68199836, 0.69465837, 0.70710678, 
  655.   0.7193398, 0.7313537, 0.74314483, 0.75470958, 
  656.   0.76604444, 0.77714596, 0.78801075, 0.79863551, 
  657.   0.80901699, 0.81915204, 0.82903757, 0.83867057, 
  658.   0.8480481, 0.8571673, 0.8660254, 0.87461971, 
  659.   0.88294759, 0.89100652, 0.89879405, 0.90630779, 
  660.   0.91354546, 0.92050485, 0.92718385, 0.93358043, 
  661.   0.93969262, 0.94551858, 0.95105652, 0.95630476, 
  662.   0.9612617, 0.96592583, 0.97029573, 0.97437006, 
  663.   0.9781476, 0.98162718, 0.98480775, 0.98768834, 
  664.   0.99026807, 0.99254615, 0.9945219, 0.9961947, 
  665.   0.99756405, 0.99862953, 0.99939083, 0.9998477, 1.};
  666.  
  667. float IntSin(int x)
  668. {
  669.     if(x < 0)   return -IntSin(-x);
  670.     if(x > 180) return -IntSin(x%180);
  671.     if(x > 90)  return  _IntSin[180-x];
  672.     return _IntSin[x];
  673. }
  674. @end
  675.